<?php

namespace app\service;


/**
 * Redis 流量限流
 */
class RedisLimit {

    private $redis; // Redis实例

    static $oneITime = 60; // 单位时间 一分钟
    static $oneIMax = 50; // 一个用户Ip一分钟最多仅允许访问访问10次
    static $platITime = 60; // 针对所有用户，单位时间内允许访问的最大次数
    static $platIMax = 10; // 针对所有的用户，该接口最多仅允许访问N次

    public function __construct() {
        try {
            if (!extension_loaded('redis')) {
                throw new \BadFunctionCallException('not support: redis');
            }
            $this->redis = redis();
        } catch (\Exception $e) {
            die('error-RedisConnect' . $e->getCode() . $e->getMessage());
        }
    }

    /**
     * 限制单用户在多少秒内的请求次数
     * @param $uid
     * @return bool
     */
    public function singleLimit($uid) {
        $ip = get_client_ip();
        $key = md5($ip . ":" . $uid);
        $script = <<<EOF
local key = KEYS[1];
local maxCount = tonumber(ARGV[1]);
local expireTime = ARGV[2];
local count = redis.call('get', key);
if (count and tonumber(count) >= maxCount)
then
    return false;
else
    redis.call('incr', key);
    return true;
end
EOF;
        return $this->redis->eval($script, [$key, self::$oneIMax, self::$oneITime], 1);

        /*$userFlowCount = $this->redis->get($key);
        if ($userFlowCount && $userFlowCount >= self::$oneIMax) {
            return false;
        }
        $this->redis->incr($key);
        if (!$userFlowCount) {
            $this->redis->expire($key, self::$oneITime);
        }
        return true;*/
    }

    /**
     * 全局限流
     */
    public function globalLimit($reqUrl) {
        $platKey = md5($reqUrl);
        $script = <<<EOF
local platKey = KEYS[1];
local platIMax = tonumber(ARGV[1]);
local platITime = ARGV[2];
local count = redis.call('get', platKey);
if (count and tonumber(count) >= platIMax)
then
    return false;
else
    local index = redis.call('incr', platKey);
    if (index == 1)
    then
        redis.call('expire', platKey, platITime);
        return true;
    end
    return true;
end
EOF;
        return $this->redis->eval($script, [$platKey, self::$platIMax, self::$platITime], 1);
    }

    /**
     * 是否允许放行 场景：限流 [压测ok]
     * @param string $key       redis键前缀
     * @param int $timeInterval 时间间隔（秒）
     * @param int $max          时间间隔内最大放行数
     * @return bool             是否放行
     * example：
     * //每秒放行一个
     * isAllow('my_allow');
     *
     * //每秒放行3个
     * isAllow('my_allow', 1, 3);
     *
     * //每3秒放行2个
     * isAllow('my_allow', 3, 2);
     */
    public function isAllow($key, $timeInterval = 1, $max = 2) {
        if ($timeInterval < 1) {
            die('时间间隔必须大于0');
        }
        if ($max < 1) {
            die('最大放行数必须大于0');
        }

        //对时间戳取模，使得每$timeInterval秒取得同一个时间戳
        $time = time();
        $key .= ':' . ($time - ($time % $timeInterval));
        debugLog($key, 'isAllowKey');

        //自增并返回自增后的结果 TODO 集群需改成lua
        $index = $this->redis->incr($key);
        //如果是第一个访问，设置键的过期时间
        if ($index === 1) {
            $this->redis->expire($key, $timeInterval + 1);
        }

        return $index < $max + 1;
    }
}
